#!/usr/bin/python2

import logging
import sys
import os
import time
import urllib
import bjoern
from cgi import escape
import mimetypes


def router(env, start_response):
    path = urllib.unquote_plus(os.path.normpath(env['PATH_INFO'][1:]))

    if not os.path.exists(path):
        start_response("404", [("Content-Type", "text/plain")])
        return ["Not Found\r\n"]

    if os.path.isdir(path):
        return index_handler(env, start_response)
    else:
        return file_handler(env, start_response)


def index_handler(env, start_response):
    path_orig = env["PATH_INFO"]
    path = urllib.unquote_plus(os.path.normpath(path_orig[1:]))

    if path == "..":
        start_response("403", [("Content-Type", "text/plain")])
        yield "Permission Denied\r\n"
        return

    start_response("200", [('Content-Type', 'text/html; charset=UTF-8')])
    yield "<html>\n<head>\n<title>Index of "
    yield path_orig
    yield "</title>\n</head>\n<body>\n<h1>Index of "
    yield path_orig
    yield "</h1>\n"

    yield '<hr />\n'
    yield '<table>\n'

    for f in (['..'] if path != '.' else []) + sorted([i for i in os.listdir(path) if not i.startswith('.')]):
        yield ('<tr><td><a href="')
        absf = os.path.join(path, f)
        yield urllib.quote_plus(f)
        if os.path.isdir(absf):
            f += '/'
            yield '/'
        yield '">'
        yield escape(f.decode("utf-8")).encode('ascii', 'xmlcharrefreplace')
        yield '</a></td><td>'
        lstat_result = os.lstat(absf)
        yield (escape(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(lstat_result.st_mtime))))
        yield '</td><td align="right">'
        yield (escape(str(lstat_result.st_size)))
        yield ('</td></tr>\n')
    yield '</table>\n<hr />\n'

    yield """
    <div>Powered by <a href="https://github.com/biergaizi/bjoern-simple-http-server" target="_blank">BjoernSimpleHTTPServer</a>
    </div>

    </body>
    </html>
    """
    return


class FilePool(object):

    MAX_OPEN_FILES = 20
    REFRESH_TIME = 30

    def __init__(self):
        self._files = []

    def open(self, path, mode):
        path = os.path.normpath(path)

        # Out of size. Eliminate the least recently used file.
        if len(self._files) + 1 > self.MAX_OPEN_FILES:
                self._files.pop().close()

        # Hit!
        for file in self._files:
            if os.path.normpath(file[0].name) == path:
                # Outdated...
                if time.time() - file[1] > self.REFRESH_TIME:
                    self._files.remove(file)
                    file[0].close()
                    break
                self._files.insert(0, self._files.pop(self._files.index(file)))
                return file[0]

        # Missed...
        file = open(path, mode), time.time()
        self._files.insert(0, file)
        return file[0]


pool = FilePool()


def file_handler(env, start_response):
    path = urllib.unquote_plus(os.path.normpath(env['PATH_INFO'][1:]))
    f = pool.open(path, "rb")

    mime = mimetypes.guess_type(f.name)[0] or mimetypes.guess_type(f.name)[1]
    if not mime:
        mime = "text/plain"
    start_response("200", [("Content-Type", mime)])

    pos = 0
    while True:
        f.seek(pos)
        buf = f.read(1024)
        pos += 1024
        f.seek(0)
        if buf:
            yield buf
        else:
            break

if __name__ == "__main__":
    listen_address = ''
    listen_port = 8000
    try:
        if len(sys.argv) == 2:
            listen_port = int(sys.argv[1])
        elif len(sys.argv) == 3:
            listen_address = sys.argv[1]
            listen_port = int(sys.argv[2])
        assert 0 <= listen_port <= 65535
    except (AssertionError, ValueError) as e:
        raise ValueError('port must be a number between 0 and 65535')

    logging.info('Listening on %s:%s' % (listen_address or '[::]' if ':' not in listen_address else '[%s]' % listen_address, listen_port))
    try:
        bjoern.run(router, listen_address, listen_port)
    except KeyboardInterrupt:
        exit()
